﻿Public Class ScrollableControl

	Public Shadows WithEvents VScroll As New VScrollBar
	Public Shadows WithEvents HScroll As New HScrollBar

	Sub New()
		InitializeComponent()
		Controls.Add(VScroll)
		Controls.Add(HScroll)
	End Sub

	Protected Overrides Sub OnCreateControl()
		MyBase.OnCreateControl()
		VScroll.Location = New Point(Width - VScroll.Width, 0)
		VScroll.Height = Height - If(HScroll.Visible, HScroll.Height, 0)
		VScroll.Anchor = AnchorStyles.Right Or AnchorStyles.Bottom Or AnchorStyles.Top
		HScroll.Location = New Point(0, Height - HScroll.Height)
		HScroll.Width = Width - VScroll.Width
		HScroll.Anchor = AnchorStyles.Right Or AnchorStyles.Bottom Or AnchorStyles.Left
		VScroll.Cursor = Cursors.Arrow
		HScroll.Cursor = Cursors.Arrow
	End Sub

	Dim _VirtualSize As Size
	Property VirtualSize As Size
		Get
			Return _VirtualSize
		End Get
		Set(ByVal Value As Size)
			_VirtualSize = Value
			ArrangeScrollBars()
		End Set
	End Property

	Overloads Sub Invalidate(ByVal Rect As Rectangle)
		Rect.Offset(-HScroll.Value, -VScroll.Value)
		MyBase.Invalidate(Rect)
	End Sub

	Property TopLeft As Point
		Get
			Return New Point(HScroll.Value, VScroll.Value)
		End Get
		Set(ByVal Value As Point)
			HScroll.Value = Value.X
			VScroll.Value = Value.Y
		End Set
	End Property

	Dim _FixedArea As Size
	Property FixedArea As Size
		Get
			Return _FixedArea
		End Get
		Set(ByVal Value As Size)
			_FixedArea = Value
			ArrangeScrollBars()
			Invalidate()
		End Set
	End Property

	Private Sub ArrangeScrollBars()
		If ClientRectangle.Height < 1 Then Exit Sub
		If VirtualSize.Height = 0 Then Exit Sub
		VScroll.LargeChange = ClientRectangle.Height
		VScroll.Maximum = VirtualSize.Height
		VScroll.Enabled = VScroll.Maximum > ClientRectangle.Height
		VScroll.Visible = VScroll.Enabled
		If VScroll.Enabled AndAlso VScroll.Value + VScroll.LargeChange > VScroll.Maximum Then VScroll.Value = VScroll.Maximum - VScroll.LargeChange
		HScroll.LargeChange = ClientRectangle.Width
		HScroll.Maximum = VirtualSize.Width
		HScroll.Enabled = HScroll.Maximum > Height
	End Sub

	Overloads ReadOnly Property ClientRectangle As Rectangle
		Get
			Return New Rectangle(FixedArea.Width, FixedArea.Height, Width - If(VScroll.Visible, VScroll.Width, 0) - FixedArea.Width, Height - If(HScroll.Visible, HScroll.Height, 0) - FixedArea.Height)
		End Get
	End Property

	Private Sub VerticalScroll_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles VScroll.ValueChanged
		Dim R = ClientRectangle
		R.Offset(-ClientRectangle.Left, 0)
		R.Width += ClientRectangle.Left
		Handle.Scroll(0, Val(sender.Tag) - VScroll.Value, R, R)
		sender.Tag = VScroll.Value
	End Sub

	Private Sub HorizontalScroll_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles HScroll.ValueChanged
		Handle.Scroll(Val(sender.Tag) - sender.Value, 0, ClientRectangle, ClientRectangle)
		sender.Tag = sender.Value
	End Sub

	Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
		MyBase.OnPaint(e)
		e.Graphics.TranslateTransform(-HScroll.Value, -VScroll.Value)
	End Sub

	Protected Overrides Sub OnMouseWheel(ByVal e As System.Windows.Forms.MouseEventArgs)
		Dim h = Int(-e.Delta / 3)
		TryVerticalScroll(h)
	End Sub

	Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
		MyBase.OnResize(e)
		ArrangeScrollBars()
	End Sub

	Sub TryVerticalScroll(ByVal Amount As Integer)
		Dim NV = VScroll.Value + Amount
		If NV < 0 Then NV = 0
		If NV > VScroll.Maximum - VScroll.LargeChange Then NV = VScroll.Maximum - VScroll.LargeChange
		If NV < 0 Then Exit Sub
		VScroll.Value = NV
	End Sub

	Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
		MyBase.OnMouseMove(e)
		If e.Button = Windows.Forms.MouseButtons.Left Then
			If e.Y < ClientRectangle.Top Then TryVerticalScroll(-VScroll.SmallChange)
			If e.Y > ClientRectangle.Bottom Then TryVerticalScroll(VScroll.SmallChange)
		End If
	End Sub

	Private Sub VScroll_VisibleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles VScroll.VisibleChanged
		Static OldValue As Boolean
		If VScroll.Visible = OldValue Then Return
		OldValue = VScroll.Visible
		OnSizeChanged(Nothing)
	End Sub
End Class

